動畫在現代網頁中無所不在。好的使用者介面,裡面的動畫應該要看起來很平順自然,必要而不花俏,這樣才能帶給使用者最佳的體驗,並且在某些重要的地方讓使用者注意到。所以一個有好的設計的動畫頁面,可以讓 UI 更有趣且更容易使用。
Angular 可以製造出跟純 CSS 動畫一樣的效果. 我們可以輕鬆的用程式碼來控制這些動畫效果,搭配一些邏輯放入我們的網頁應用程式。
Angular 動畫使用的模組已經內建在大多數瀏覽器,若是不支援要加入 web-animations.min.js
接下來我們竟來建立 component,來做到用淡入的效果顯示和隱藏內容,並讓其他外部的 component 也可以輕易的觸發他們的淡入效果。
先來看看做動畫需要甚麼吧! 畢竟巧婦難為無米之炊。
首先要引入這些東西
import {
Component,
Input,
trigger,
state,
style,
transition,
animate
} from '@angular/core';
先來看看簡單的隱藏顯示,並未加入動畫。
@Component({
selector: 'my-fader',
template: `
<div *ngIf="visibility == 'shown'" >
<ng-content></ng-content>
Can you see me?
</div>
`
})
export class MyComponent implements OnChanges {
visibility = 'shown';
@Input() isVisible : boolean = true;
ngOnChanges() {
this.visibility = this.isVisible ? 'shown' : 'hidden';
}
}
這邊用 @Input() isVisible
屬性來達大顯示和隱藏的效果。(Plurk)
只是單純的顯示和隱藏多麼地無趣呀!我們想要有淡入或淡出的效果。
來幫 Component 加點料吧!
@Component({
...,
template : ``,
animations: [
...
]
)]
class MyComponent() { ... }
animations
用來宣告動畫,和 template
一樣屬於 metadata。
因為我們的動畫是 visibility
的屬性變化,所以我們的動畫設定由其值變化而觸發。
animations: [
trigger('visibilityChanged', [
state('shown' , style({ opacity: 1 })),
state('hidden', style({ opacity: 0 }))
])
]
精神一切盡在程式碼中。
但是剛剛的設定,是瞬間變化,而我們希望的是慢一點的樣子
animations: [
trigger(’visibilityChanged', [
state('shown' , style({ opacity: 1 })),
state('hidden', style({ opacity: 0 })),
transition('* => *', animate('.5s'))
])
]
這樣不管淡入 (opacity 1 到 0 ) 或是淡出 (opacity 0 到 1 ) 都會經過 500ms,而 animate
也可以這樣表示 animate('500ms')
。transition('* => *', ...)
代表甚麼意思呢?*
代表任何狀態,意思就是可以是 shown
或 hidden
。當然也可以直接指定。
animations: [
trigger(’visibilityChanged', [
state('shown' , style({ opacity: 1 })),
state('hidden', style({ opacity: 0 })),
transition('shown => hidden', animate('600ms')),
transition('hidden => shown', animate('300ms')),
])
]
這樣我們就把淡入和淡出做區隔,兩者時間花的不一樣長。
接下來要把動畫和 Compoent 連起來。 visibilityChanged
是如何和 component 連結?動畫又是如何和和 component 的屬性作綁定?
我們可以在模板裡這樣做,就像許多的數據綁定模式雷同。
<div [@visibilityChanged]="visibility">
Can you see me? I should fade in or out...
</div>
完整版就會長得像這樣子
import {
Component, OnChanges, Input,
trigger, state, animate, transition, style
} from '@angular/core';
@Component({
selector : 'my-fader',
animations: [
trigger('visibilityChanged', [
state('shown' , style({ opacity: 1 })),
state('hidden', style({ opacity: 0 })),
transition('* => *', animate('.5s'))
])
],
template: `
<div [@visibilityChanged]="visibility" >
<ng-content></ng-content>
<p>Can you see me? I should fade in or out...</p>
</div>
`
})
export class FaderComponent implements OnChanges {
@Input() isVisible : boolean = true;
visibility = 'shown';
ngOnChanges() {
this.visibility = this.isVisible ? 'shown' : 'hidden';
}
}
用 [@visibilityChanged]="isVisible"
而不是 [@visibilityChanged]="visibility"
的話,可以不用到 ngOnChanges
,意思就是程式碼可以更精簡。但要注意的是 isVisible 的狀態是 true
和 false
import {
Component, OnChanges, Input,
trigger, state, animate, transition, style
} from '@angular/core';
@Component({
selector : 'my-fader',
animations: [
trigger('visibilityChanged', [
state('true' , style({ opacity: 1, transform: 'scale(1.0)' })),
state('false', style({ opacity: 0, transform: 'scale(0.0)' })),
transition('1 => 0', animate('300ms')),
transition('0 => 1', animate('900ms'))
])
],
template: `
<div [@visibilityChanged]="isVisible" >
<ng-content></ng-content>
<p>Can you see me? I should fade in or out...</p>
</div>
`
})
export class FaderComponent implements OnChanges {
@Input() isVisible : boolean = true;
}
(Plurk)
parent component 可以直接控制 child component,這邊的例子就是 <my-fader>
的 isVisible
變化,會直接影響到 child component my-fader
,my-fader
的模板也會跟著一起動,達到我們要的淡入淡出效果。
@Component({
selector : 'my-app',
template: `
<my-fader [isVisible]="showFader">
Am I visible ?
</my-fader>
<button (click)="showFader = !showFader"> Toggle </button>
`
})
export class MyAppComponent {
showFader : boolean = true;
}
(Plurk)
還記得剛剛 transition('* => *',...)
嗎? *
就是 wildcard state(通用符號),也就是表示所有狀況,在程式語言中應該蠻常見的。
void
代表沒有東西,可能是被移除或是還沒被產生,void
用在進入和出場時非常好用。舉例來說 * => void
就代表離開畫面的動畫。
void => *
* => void
animations: [
trigger('flyInOut', [
state('in', style({transform: 'translateX(0)'})),
transition('void => *', [
style({transform: 'translateX(-100%)'}),
animate(100)
]),
transition('* => void', [
animate(100, style({transform: 'translateX(100%)'}))
])
])
]
上面的程式碼,會先有飛入效果,緊接者飛出。
看起來就會長這樣:
Angular 提供三種控制時間效果的屬性,分別是 duration
、 delay
和 easing
。
animations: [
trigger('flyInOut', [
state('in', style({opacity: 1, transform: 'translateX(0)'})),
transition('void => *', [
style({
opacity: 0,
transform: 'translateX(-100%)'
}),
animate('0.2s ease-in')
]),
transition('* => void', [
animate('0.2s 10 ease-out', style({
opacity: 0,
transform: 'translateX(100%)'
}))
])
])
]
看起來就會長這樣
大多數的動畫都可以用 Angular Component 的方式做到,而不需要動到 TypeScript 來控制動畫效果,Angular 動畫設計的目的就是讓我們用更直觀,不直接動 DOM 的方式,用 component->template->animation 的方式,讓設計師更好建構和實現動畫。
※更詳盡內容可以參考官方文件
嗨你好,想請問一下,雖然這個功能真的滿強大的,但是不是用最原生的transition
就可以解決visibility+opcaity
的問題,動畫這個功能是不是顯得有點雞肋?
還是說他有比較特定需要他出現的地方或用途?
我覺得就是看個人喜好吧,動畫也可以用 d3 來做,端看習慣和方便
嗨你好,想請問一下,雖然這個功能真的滿強大的,但用最原生的transition
就可以解決visibility+opcaity
的問題,動畫這個功能是不是顯得有點雞肋?
還是說他有比較特定需要他出現的地方或用途?